Skip to content

AutoOr 反解析

本页所有地址都适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build libtpu_lts_20260413_b_RC00,build-id md5 89edbbe81c5b328a958fe628a9f2207d,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 —— +1 bool,+4 int32/uint32/float/enum,+8 int64/double,+0x20 string,+0x28..+0x50 message。这是 resolver 写入的同一打包布局。
  • 每个类型的规范打印 spelling —— "true"/"false"(不是 1/0)、有符号十进制(总是如此,无 radix 标记)、带 inf/nan 特殊 token 的最短 round-trip float/double、原样 string、注册 enum value NAME 或裸十进制回退。
命名目标xla::jellyfish::AbslUnparseFlag<AutoOr<bool>> @ 0x1d6ba240cmpb $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) @ 0x21113380FastIntToBuffer → 有符号十进制
float/double 原语UnparseFloatingPointVal<float> @ 0x21113460 / <double> @ 0x211135a0"%.*g" + SimpleAtof/SimpleAtod round-trip 检查
string 原语AutoOrTypeTraits<string>::Unparse @ 0x1d743c20AbslUnparseFlag(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 把该打包值变回字符串。每个类型类有一个实例,全部共享同一个骨架。

入口点

text
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 是规范主体。反编译精确显示了双分支形状:

c
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 — resolverFromProtoOrDie)中存在的 "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 字节打包布局
boolAbslUnparseFlag<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 — floatint32/enum 共享 +0x04 的 8 字节打包槽,不是 int64/double+0x08 has-byte 槽。float 是 4 字节,因此它的 present bit 适合放在 8 字节 pack 的 bit 32(present 字节 +4);double 是 8 字节,需要单独的 has-byte 位于 +8。如果重新实现者把“floating point ⇒ has-byte at +8”混为一谈,就会从错误字节读取 float present 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:uint64BufferContentsSanitizerConfigPrecisionTracerModeProto_ValueSkipConfigTypeProto_Value
  • 2 个 arm 被链接器 identical-code-folded 到逐字节相同的兄弟函数上,符号消失,代码共享:ExecutionOptions_EffortLevel(折叠到另一个 (present<<32)|val32 + enum AbslUnparseFlagImpl)和 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

c
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 sret

val ^ 5 无分支地计算字符串长度:false 是 5 个字符,true 是 4 个字符,且 0^5=51^5=4。字面量按值选择(类似 cmov)。"true"@0x867b321 / "false"@0x868fb8a 字面量与 bool 输入(SimpleAtob)识别的是同一组 .rodata 字节,因此 round-trip 在同一个字节序列上闭合。

int32 / int64 → 有符号十进制(总是如此)

Unparse(int) @ 0x211132c0Unparse(long) @ 0x21113380 都会清零 SSO 字符串,将长度设为 22,然后调用 FastIntToBuffer@ 0x211719e0 / @ 0x21171f00)以 base-10 写入,负数带前导 -

c
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 sret

QUIRK — unparse 总是十进制,但输入接受 0x radix 和符号(§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 做原样复制:

c
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 中查找编号并打印注册名称,否则回退到裸十进制:

c
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 结构相同):

c
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 sret

double 路径使用 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"被接受
nanexp 全一,mantissa ≠ 0"nan"被接受
finite否则重新解析到相同 bits 的最短 %g 形式由构造保证恒等

QUIRK — inf/nan 分支绕过 round-trip 检查:非有限值直接作为其 %g special 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。 输入接受 0x hex 和符号;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 @ 0x20ef2e40 unparse(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 字面量取得字符。

算法

c
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 @ 0x2257ea50int @ 0x2257ecb0long @ 0x2257ec30string @ 0x2257ec50ScavengingMode @ 0x2257ee30CostModelFlagOptions @ 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) @ 0x21113200bool 原语:通过 xor-$5 发出 "true"/"false"
UnparseFloatingPointVal<float>/<double> @ 0x21113460/0x211135a0float/double 最短 round-trip + inf/nan token
AbslUnparseFlagImpl(int, EnumDescriptor&) @ 0x20ec7220enum 原语:value NAME / 裸十进制
"auto" @ 0x85ca81f共享 AUTO 字面量:与输入哨兵相同字节
Message::AbslUnparseFlagImpl @ 0x20ef2e40message-arm proto-text unparse(由 message-arms 页面负责)

交叉引用