AutoOr 反解析
本页所有地址都适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(buildlibtpu_lts_20260413_b_RC00,build-id md589edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,ELF x86-64 DYN,未 strip;反修饰 C++ 符号按原样引用)。其他版本会有所不同。
摘要
三态 TPU 编译 knob 会存储为一个打包的 AutoOr<T> 值(§resolution model),并由输入语法从 flag 字符串解析而来(§parse grammar)。本页负责反向边:xla::jellyfish::AbslUnparseFlag<AutoOr<T>>,即 25 个不同函数组成的家族,它们把已存储的 AutoOr<T> 渲染回用户在 --helpfull 或 flag dump 中看到的显示字符串,并且该字符串必须能被再次解析接受。这个契约在设计上是对称的:每个打印出的 spelling 都是规范输入 token,因此一个值被写入、dump、再重新喂入后,会 round-trip 到同一个 AutoOr<T>(有两个已记录的例外)。
unparse 家族是统一的。每个实例都接收 (std::string* sret /*rdi*/, AutoOr<T> const& /*rsi*/),开头用一个 cmpb $0x0, OFF(%rsi) 测试该类型类的 present/has 字节,然后分支:如果该字节为零,值就是 AUTO,函数会从按类型 lazy-static 的 absl::NoDestructor<std::string> 构建字面量 "auto",其来源是共享的 .rodata 字面量(@ 0x85ca81f = "auto");如果该字节已设置,则分派到 T 的标准 absl/proto2 unparse 原语。present-byte offset 与 resolver 打包的位置相同(bool 的 bit 8,4 字节类的 bit 32,int64/double/string/message 的配对 has-byte)—— unparser 读取的是 consumer accessor 测试的同一套打包布局(§resolution)。
参照框架是普通 scalar 的 absl::AbslUnparseFlag,但外层包了一层,使得 AUTO present 检查先触发,并在内部类型 unparser 运行前短路到固定字符串 "auto"。本页结构如下:家族骨架和按类型类的 present-byte 表;按类型类的原语(bool xor-trick、有符号十进制整数、UnparseFloatingPointVal round-trip、原样 string、enum value-NAME);float/double 边界 token(inf/nan、最短 round-trip、radix 非对称);以及重新实现者必须接受的 round-trip 退化。解析方向在 autoor-parse-grammar.md 上与此镜像;message-arm proto-text 语法(text:/serialized:/base64:)在 autoproto-message-arms.md 上。
重新实现时,契约是:
- 双分支骨架 —— 在 present/has 字节上执行
cmpb $0x0, OFF(%rsi);为零 ⇒ 从共享 lazy-static 发出"auto";已设置 ⇒ 在 value 字节上调用类型化原语。 - 按类型类的 present-byte offset ——
+1bool,+4int32/uint32/float/enum,+8int64/double,+0x20string,+0x28..+0x50message。这是 resolver 写入的同一打包布局。 - 每个类型的规范打印 spelling ——
"true"/"false"(不是1/0)、有符号十进制(总是如此,无 radix 标记)、带 inf/nan 特殊 token 的最短 round-trip float/double、原样 string、注册 enum value NAME 或裸十进制回退。
| 命名目标 | xla::jellyfish::AbslUnparseFlag<AutoOr<bool>> @ 0x1d6ba240 — cmpb $0,+1;set ⇒ Unparse(bool);clear ⇒ "auto" |
| 家族大小 | 25 个不同的 AbslUnparseFlag<AutoOr<T>> 符号(30 个 oneof arm → 25 个 unparse 符号;§ICF) |
| AUTO 字面量来源 | "auto" @ 0x85ca81f(.rodata)—— 每个实例和输入哨兵共享一个字节序列 |
| bool 原语 | absl::flags_internal::Unparse(bool) @ 0x21113200 — "true"@0x867b321 / "false"@0x868fb8a,xor-$5 size trick |
| int/long 原语 | Unparse(int) @ 0x211132c0 / Unparse(long) @ 0x21113380 → FastIntToBuffer → 有符号十进制 |
| float/double 原语 | UnparseFloatingPointVal<float> @ 0x21113460 / <double> @ 0x211135a0 — "%.*g" + SimpleAtof/SimpleAtod round-trip 检查 |
| string 原语 | AutoOrTypeTraits<string>::Unparse @ 0x1d743c20 → AbslUnparseFlag(string_view) @ 0x211136e0(原样) |
| enum 原语 | proto2::internal::AbslUnparseFlagImpl(int, EnumDescriptor&) @ 0x20ec7220 → value NAME / 裸十进制 |
| Present-byte offsets | +1 bool · +4 int32/uint32/float/enum · +8 int64/double · +0x20 string · +0x28..+0x50 message |
| 置信度 | CONFIRMED(字节锚定 vs 反编译),除非某行或 callout 另有说明 |
1. AbslUnparseFlag<AutoOr<T>> 家族
目的
xla::jellyfish::AbslUnparseFlag(std::string* sret, AutoOr<T> const& v) 是 absl flags 机制在字符串化存储类型为 AutoOr<T> 的 flag 时调用的函数。它是输入分派器的对称逆操作(§parse grammar):输入把字符串变成打包的 (present, value);unparse 把该打包值变回字符串。每个类型类有一个实例,全部共享同一个骨架。
入口点
AbslUnparseFlag<AutoOr<T>>(std::string* sret, AutoOr<T> const& v) ── e.g. bool @ 0x1d6ba240
├─ cmpb $0x0, OFF(%rsi) ── present/has byte for T's class (§1 table)
├─ je → build "auto" ── lazy NoDestructor<string> from "auto"@0x85ca81f
│ (__cxa_guard_acquire + PlacementImpl, then SSO/heap copy into sret)
└─ jne → typed primitive on the value bytes ── Unparse(bool)/Unparse(int)/UnparseFloatingPointVal/...算法
bool 实例 @ 0x1d6ba240 是规范主体。反编译精确显示了双分支形状:
function AbslUnparseFlag<AutoOr<bool>>(string* sret, AutoOr<bool>* v): // 0x1d6ba240
if v[1]: // present byte at +1 — bit 8 of (present<<8)|val8
Unparse(sret, *v) // 0x21113200, value byte at +0 → "true"/"false"
else: // AUTO
once: // __cxa_guard_acquire(auto_flag_value)
NoDestructor<string>::PlacementImpl(auto_flag_value, "auto") // "auto" @ 0x85ca81f
copy auto_flag_value into sret // SSO if len<=22 else heap; NUL-terminate
return sret被测试的 present 字节是 resolver 的 present-bit,位于该 bit 所在的字节:(present<<8)|val8 的 bit 8 是字节 +1,所以 bool unparser 测试 v[1] 并从 v[0] 读取值。value 字节就是 resolver 打包的内容;unparser 从不重新读取 AutoProto oneof,而是纯粹作用于 consumer 持有的打包 AutoOr<T>。
NOTE — resolver(
FromProtoOrDie)中存在的"OrDie"fatal path 在这里没有对应物。unparser 不会失败:present 字节为零就是"auto",present 字节已设置就是类型化值。25 个 unparse 主体中都没有错误字符串。
每个类型类的 present/has 字节
present/has 字节位于类型类特定的 offset,因为打包布局不同;unparse 的 cmpb offset 就是该布局的逐字节证据。下面从全部 25 个家族成员开头的 cmpb $0x0, OFF(%rsi) 提取:
| 类型类 | Unparse 符号 | Present/has 字节 | 打包布局 |
|---|---|---|---|
bool | AbslUnparseFlag<AutoOr<bool>> @ 0x1d6ba240 | +0x01 | (present<<8)|val8 |
int32 | @ 0x1d744180 | +0x04 | (present<<32)|val32 |
uint32 | @ 0x1d743f60 | +0x04 | (present<<32)|val32 |
float | @ 0x1d743d40 | +0x04 | (present<<32)|f32 |
int64 (long) | @ 0x1d743560 | +0x08 | {value@+0, has@+8} |
double | @ 0x1d744ce0 | +0x08 | {value@+0, has@+8} |
string | @ 0x1d743b00 | +0x20 | {body, variant-idx@+0x18, has@+0x20} |
| enum(7 个 arm) | @ 0x1d748380 etc. | +0x04 | (present<<32)|val32 |
| message(11 个 arm) | @ 0x1d7453c0 etc. | +0x28..+0x50 | {variant idx, sub-msg, has} |
GOTCHA —
float与int32/enum共享+0x04的 8 字节打包槽,不是int64/double的+0x08has-byte 槽。float是 4 字节,因此它的 present bit 适合放在 8 字节 pack 的 bit 32(present 字节+4);double是 8 字节,需要单独的 has-byte 位于+8。如果重新实现者把“floating point ⇒ has-byte at +8”混为一谈,就会从错误字节读取floatpresent bit。float打包为(present<<32)|f32,证据是AbslUnparseFlag<AutoOr<float>> @ 0x1d743d40测试+4并用vmovss从+0加载值。
为什么 30 个 arm → 25 个 unparse 符号 {#why-30-arms--25-unparse-symbols}
AutoProto oneof 有 30 个 arm,但只存在 25 个不同的 unparse 符号:
- 4 个 arm 没有注册 flag(仅 oneof 内部使用,从不是 CLI flag),因此没有发出 unparse:
uint64、BufferContentsSanitizerConfig、PrecisionTracerModeProto_Value、SkipConfigTypeProto_Value。 - 2 个 arm 被链接器 identical-code-folded 到逐字节相同的兄弟函数上,符号消失,代码共享:
ExecutionOptions_EffortLevel(折叠到另一个(present<<32)|val32+ enumAbslUnparseFlagImpl)和SparseCoreAssertLevel(enum-class arm;它的AbslUnparseFlag<AutoOr<…>>符号同样缺失,折叠到逐字节相同的兄弟函数)。
NOTE — ICF 意味着重新实现者不能推断“每个 arm 都有自己的 unparse 函数”。两个 enum/message arm 运行的是另一个 arm 的字节。行为不变(代码逐字节相同),但按符号逐个重建会少两个名字。
2. scalar 原语
目的
一旦 present 字节已设置,家族成员就会在 value 字节上调用 T 的标准 absl 原语。这些原语不是 TPU 特有的,而是 stock absl::flags_internal unparser;但它们产生的精确 spelling 是 round-trip 契约,所以这里逐字节走查每一个。
bool → "true" / "false"(xor-$5 trick)
absl::flags_internal::Unparse(bool) @ 0x21113200 发出单词,永远不是 1/0:
function Unparse(string* sret, unsigned val): // 0x21113200
len = val ^ 5 // 0 (false) → 5; 1 (true) → 4
sret.sso_size = val ^ 5 // byte at sret+23 (SSO length slot)
src = val ? "true"@0x867b321 : "false"@0x868fb8a
memcpy(sret, src, len) // copy exactly len bytes
sret[len] = 0
return sretval ^ 5 无分支地计算字符串长度:false 是 5 个字符,true 是 4 个字符,且 0^5=5、1^5=4。字面量按值选择(类似 cmov)。"true"@0x867b321 / "false"@0x868fb8a 字面量与 bool 输入(SimpleAtob)识别的是同一组 .rodata 字节,因此 round-trip 在同一个字节序列上闭合。
int32 / int64 → 有符号十进制(总是如此)
Unparse(int) @ 0x211132c0 和 Unparse(long) @ 0x21113380 都会清零 SSO 字符串,将长度设为 22,然后调用 FastIntToBuffer(@ 0x211719e0 / @ 0x21171f00)以 base-10 写入,负数带前导 -:
function Unparse(string* sret, int val): // 0x211132c0
sret = ""; sret.sso_size = 22 // room for INT64_MIN + sign + NUL
end = FastIntToBuffer(val, sret.buf) // base-10, '-' prefix if negative
sret.size = end - sret.buf
sret[size] = 0
return sretQUIRK — unparse 总是十进制,但输入接受
0xradix 和符号(§parse grammar)。因此以0x10设置的 knob 会 unparse 为"16",以0x7fffffffffffffff设置的INT64_MAX会 unparse 为"9223372036854775807"。这种 radix 非对称是整数 otherwise-canonical round-trip 中唯一的非恒等处:值会保留,spelling 会规范化为十进制。
string → 原样(带 "auto" 退化)
AutoOrTypeTraits<string>::Unparse @ 0x1d743c20 读取 +0x18 处的 variant index(*((BYTE*)v + 24):0 = owned std::string,1 = string_view),两种情况都委托给 absl::flags_internal::AbslUnparseFlag(string_view) @ 0x211136e0 做原样复制:
function AutoOrTypeTraits<string>::Unparse(string* sret, variant* v): // 0x1d743c20
idx = v[24] // variant index byte
if idx == 1: // string_view arm
copy v's view into a temp; AbslUnparseFlag(sret, temp)
elif idx == 0: // owned std::string arm
read body (SSO at v+23 or heap ptr at v[0]); AbslUnparseFlag(sret, body)
else: __throw_bad_variant_access
return sret // verbatim — no escaping, no quoting外层 AbslUnparseFlag<AutoOr<string>> @ 0x1d743b00 测试 +0x20 的 has-byte;为零 ⇒ "auto"。
GOTCHA — 因为
AUTO会 unparse 为字面量"auto",而 string 输入是原样复制,存储值正好为"auto"的 string 会 unparse 为"auto"并重新解析为AUTO。这是不可恢复的 round-trip 退化:string 值"auto"与 AUTO 哨兵不可区分。重新实现者必须接受"auto"是 string-knob 的保留值;没有转义机制。
enum → value NAME,未知编号 → 裸十进制
proto2::internal::AbslUnparseFlagImpl(int number, EnumDescriptor const& d) @ 0x20ec7220 在 descriptor 中查找编号并打印注册名称,否则回退到裸十进制:
function AbslUnparseFlagImpl(string* sret, int number, EnumDescriptor* d): // 0x20ec7220
node = EnumDescriptor::FindValueByNumber(d, number) // 0x20e58060
if node:
name = *(string*)(node + 8) // EnumValueDescriptor name; SSO at +0x17
copy name into sret // e.g. "AGGRESSIVE", "VERIFY"
else: // numeric value with no registered name
sret = ""; FastIntToBuffer(number, sret) // bare decimal fallback
return sret这与 enum 输入(FindValueByName,带 lower/upper-case 重试 + numeric fallback)配对:用任意大小写名称给出的 enum knob 会 round-trip 到其注册规范大小写 NAME;没有注册名称的数字会 round-trip 为裸数字。enum 值从 (present<<32)|val32 pack 的 bit 0..31 读取;字节 +4 的 present bit 决定 enum 原语是否运行(为零 ⇒ "auto")。
3. float / double 边界 token 格式化
目的
UnparseFloatingPointVal<T> 是唯一具有非平凡数值边界行为的原语,也正是在这里,天真的重新实现会偏离。它是 absl 的最短 round-trip 浮点打印器:用某个精度格式化,然后通过解析自己的输出并与原始 bits 比较来验证 round-trip。float 和 double 实例除 IEEE-754 special-value mask 宽度以及 SimpleAtof vs SimpleAtod 重新解析之外,结构逐字节相同。
算法
UnparseFloatingPointVal<float> @ 0x21113460(<double> 主体 @ 0x211135a0 结构相同):
function UnparseFloatingPointVal<float>(string* sret, float x): // 0x21113460
bits = bit_cast<uint32>(x)
tmp = FormatPack("%.*g", PREC, x) // 0x... str_format; format to a temp string
if (bits & 0x7FFFFFFF) >= 0x7F800000: // exponent all-ones ⇒ inf / nan
sret = tmp // emit the special token verbatim (inf/-inf/nan)
return sret
// finite path — verify the formatted text round-trips to the same value
if SimpleAtof(tmp, &back): // 0x21171440 — re-parse our own output
vucomiss(back, x) // compare back vs original (NaN-aware compare)
sret = FormatPack("%.*g", PREC, x) // emit (precision honored, shortest faithful form)
return sretdouble 路径使用 mask 0x7FFFFFFFFFFFFFFF >= 0x7FF0000000000000(11-bit exponent 全一)以及 SimpleAtod @ ... + vucomisd。两者都已按字节确认。
边界 token
| 输入值 | 测试 | 打印 token | 重新解析 |
|---|---|---|---|
+inf | (bits & exp-mask) >= exp-all-ones,mantissa 0 | "inf"(%g infinity spelling) | 被 SimpleAtof/SimpleAtod 接受 |
-inf | 相同,sign set | "-inf" | 被接受 |
nan | exp 全一,mantissa ≠ 0 | "nan" | 被接受 |
| finite | 否则 | 重新解析到相同 bits 的最短 %g 形式 | 由构造保证恒等 |
QUIRK — inf/nan 分支绕过 round-trip 检查:非有限值直接作为其
%gspecial token 发出,绝不会重新解析。只有 finite 路径运行SimpleAtof/SimpleAtod+vucomiss/vucomisd来确认打印文本会 round-trip。这意味着 float 类型的AUTO-or knob 可以合法持有并打印inf/nan;重新实现者必须把它们作为 float/double arm 的有效存储值接受,而不是当作解析错误拒绝。NOTE — 反编译出的
FormatPack精度参数在 float 和 double 主体中都显示为立即数4("%.*g", 4)。对float而言,精度 4(约等于最小有效数字)作为最短 round-trip seed 是合理的;对double而言,字面量4太低,无法让每个 double 都 round-trip,后面的SimpleAtod+vucomisd重新解析并比较就是在 mismatch 时扩大精度的循环。精确扩大规则(absl 的RoundTripDtoA/FtoA)没有在上面 round-trip 检查之外继续逐字节走查;应把常量4视为 seed precision,而不是最终精度(精确扩大规则为 LOW,round-trip-verify 结构为 CONFIRMED)。
integer-sentinel 交互
18 个 int64-sentinel knob(§resolution Idiom C)以及 7 个 double / 12 个 float knob 在 present 时都会通过这些原语 unparse。像 INT64_MAX 这样的哨兵在 unparse 时不会特殊处理,它只是有符号十进制 9223372036854775807。哨兵含义(“AUTO 选择了此默认值”)是消费者约定,对 unparser 不可见;如果用户显式把值设为 INT64_MAX,present 字节已设置,它会 unparse 为字面数字,而不是 "auto"。
4. 文本 round-trip 表
这是面向用户的参考:每个类型类在 AUTO 与 present value 时打印什么,以及输入会把该字符串重新解析成什么。已对照 25 个家族主体和解析语法确认。
| 类型 | AUTO 打印 | present value 打印 | 重新解析为 |
|---|---|---|---|
bool | "auto" | "true" / "false" | present/value (0x101/0x100) |
int32 | "auto" | 有符号十进制(例如 "-1") | (v|0x100000000) |
uint32 | "auto" | 十进制 | (v|0x100000000) |
float | "auto" | shortest-RT float,或 inf/-inf/nan | (present<<32)|f32(字节 +4) |
int64 | "auto" | 有符号十进制 | {v, has=1}(字节 +8) |
double | "auto" | shortest-RT double,或 inf/-inf/nan | {v, has=1}(字节 +8) |
string | "auto" | string 原样 | {body, idx 0, has 1}(字节 +0x20) |
| enum | "auto" | value NAME(注册大小写)/ 无名时裸十进制 | (number|0x100000000) |
| message | "auto" | "text:<TextFormat>" / "serialized:…" / "base64:…" | sub-message + has |
重新实现者必须复现三种非对称:
- Radix。 输入接受
0xhex 和符号;unparse 总是十进制。true/false(不是1/0)是两个方向的规范 bool spelling。 - Enum 大小写。 输入大小写不敏感(lower/upper 重试);unparse 发出匹配 value name 的注册规范大小写。
"auto"string 退化。 等于"auto"的已存储 string 会 unparse 为"auto"并重新解析为AUTO。这是唯一丢失值的 round-trip。
NOTE — message arm 通过
proto2::Message::AbslUnparseFlagImpl @ 0x20ef2e40unparse(text:经由TextFormat::Printer::PrintToString,当 text-format 无法忠实 round-trip unknown field 或 map 时回退到serialized:/base64:)。该 proto-text 语法以及内部字段 spelling 由 autoproto-message-arms.md 负责;本页只记录 message present/has 字节位于+0x28..+0x50,以及AUTO答案是空默认实例并打印为"auto"。
5. AUTO 字面量
目的
25 个家族成员都会为 unset 分支从按类型 lazy-static absl::NoDestructor<std::string> 构建字符串 "auto",但它们都从一个共享 .rodata 字面量取得字符。
算法
function emit_auto(string* sret): // the je branch of every family member
if not guard.auto_flag_value: // __cxa_guard_acquire(per-type guard)
NoDestructor<string>::PlacementImpl(per_type_auto_flag_value, "auto") // src @ 0x85ca81f
guard.release()
copy per_type_auto_flag_value into sret // SSO (len 4 ≤ 22) — fits inline, no heap
return sret按类型 statics(例如 bool @ 0x2257ea50、int @ 0x2257ecb0、long @ 0x2257ec30、string @ 0x2257ec50、ScavengingMode @ 0x2257ee30、CostModelFlagOptions @ 0x2257ed10)是不同对象,每个实例化一个;但每个都通过 __cxa_guard_acquire/release + PlacementImpl 从同一个源字面量 "auto" @ 0x85ca81f 构造一次。这是输入语法用于其 AUTO 哨兵的精确字节序列,因此 AUTO round-trip 在一个 .rodata 字符串上闭合。
NOTE — 按类型 lazy-statics 是为每个
AutoOr<T>模板化AutoFlagValue()一次的产物;每个实例化都有自己的static std::string。它们都保存"auto";这种重复无害,重新实现可以把它们折叠成一个 global。必须保留的是 unset 分支精确发出"auto"(小写,四字节),因为这是解析器映射回 AUTO 的唯一 spelling。
相关组件
| 组件 | 关系 |
|---|---|
AbslUnparseFlag<AutoOr<bool>> @ 0x1d6ba240 | 家族骨架:present-byte 检查,然后类型化原语 |
Unparse(bool) @ 0x21113200 | bool 原语:通过 xor-$5 发出 "true"/"false" |
UnparseFloatingPointVal<float>/<double> @ 0x21113460/0x211135a0 | float/double 最短 round-trip + inf/nan token |
AbslUnparseFlagImpl(int, EnumDescriptor&) @ 0x20ec7220 | enum 原语:value NAME / 裸十进制 |
"auto" @ 0x85ca81f | 共享 AUTO 字面量:与输入哨兵相同字节 |
Message::AbslUnparseFlagImpl @ 0x20ef2e40 | message-arm proto-text unparse(由 message-arms 页面负责) |
交叉引用
- overview.md — 三层配置流水线;本页是 Stage 3 的 unparse OUT 边
- autoor-parse-grammar.md — 镜像:string →
AutoOr<T>输入(auto/true/false/literal/0x-hex) - autoproto-autoor-resolution.md — 解析模型;本页读回的打包
(present, value)布局 - autoproto-message-arms.md — 30-arm oneof 和 message-arm
text:/serialized:/base64:proto-text 语法 - tpu-compilation-environment.md — 承载支撑 330 个 AutoOr flag 的
AutoProto*field 的 TCE - tce-field-offsets-defaults.md — 经由该家族 round-trip 的 knob 的字节精确 field#→offset→default 映射