调查对象:
D:\code\kimicc(GitHubmoonbitlang/kimicc,MoonBit 官方组织仓库) 调查方法:主会话亲读(README×2 / AGENTS.md / docs×6 / git 历史 / mooncakes 状态 / LICENSE)+ 五个并行探索代理分维度深挖(预处理器 / 解析器与 AST / MIR 与 ctype / codegen 与 JIT / 测试防线与工程纪律),全部结论带文件:行号证据 总裁定:有实质参考价值,但价值维度不在实现复用——参考面是方法学、惯用法、防线形态与架构决策样本;kimicc 不能当 oracle(真值仍是 Clang),bobzhang/cfront 依赖禁入 Vitro 核心链 诚实声明:本机(ARM64 Windows)与其主支持目标 ARM64 macOS 不匹配,未构建或运行 kimicc——全部结论基于静态阅读与其文档/提交历史的交叉验证
1. kimicc 项目档案(实测事实清单)
| 项 | 事实 |
|---|---|
| 归属 | github.com/moonbitlang/kimicc(moonbitlang 官方组织),主理人 bobzhang(张宏波,MoonBit 创始人) |
| 许可证 | Apache-2.0;Vitro 为 MIT——只读码学风格不受限,不抄实现(逐行搬运会引入归属义务) |
| 活跃度 | 1467 commits,2026-05-03 首提交 → 2026-09-25 仍在提交,极活跃 |
| 规模 | 约 12 万行 MoonBit:cfront 26k / mir 26k / mir_codegen 14k / codegen 31.5k / test 36k / msl 1.5k / jit 0.4k |
| 定位 | MoonBit 写的小型 C 编译器 + cfront「C 元编程语法套件」(parse C → AST → 从 MoonBit 构建/变换 → 打印回 C;cfront 已独立发布 mooncakes bobzhang/cfront@0.3.1) |
| 出口 | Darwin ARM64 汇编 / Mach-O 可重定位对象(文本汇编→重新解析→指令编码,见 §4.4)/ 原生 JIT image(mmap+dlsym);实验 linux-amd64;MSL(Metal)方言发射 |
| 能力上界 | SQLite 3.49.1 conformance:编译 sqlite3.o 链进官方 Tcl testfixture,veryquick/full 跑通且残余失败与 clang 构建完全一致(见 §4.5) |
| 覆盖面 | 预处理器全套(变参宏/__VA_OPT__/#pragma once/linemarker/_Pragma)、C11 _Generic、typeof、__auto_type、位域、_Atomic 全族、__int128、va_list 全套、computed goto、alloca、printf 家族语义建模、__builtin_* 家族 |
| workspace | moon.work 双模块:bobzhang/cfront(target/ctype/preprocessor/parser/printer/qc_test,无后端依赖)+ 根 bobzhang/kimicc(mir/mir_codegen/codegen/jit/msl/cmd) |
| 工程纪律 | warnings = "+missing_doc+unnecessary_annotation+prefer_readonly_array";deprecation 130→0 一次性清零(提交 0fefb49);deprecated 块集中 deprecated.mbt(当前零文件);CI 双 diff-gate(见 §4.5) |
与 mbtcc(moonbitlang 官方 MoonBit C11 编译器,2026-09 已裁定)的关键区分:mbtcc 是 chibicc 式 token 流且停滞 8 个月;kimicc 活跃、是语言作者本人代码、覆盖预处理器——是「MoonBit 处理 C」的一手活样本。
2. 总裁定与价值结构
参考什么:MoonBit 惯用法一手范本(§4.2/§4.3)、一致性防线与测试金字塔形态(§4.5)、错误处理与 AST 忠实性等架构决策样本(§4.2/§4.3)、模板 JIT 基础设施先例(§4.4)。
不参考什么:汇编出口全套(寄存器纪律/ABI/Mach-O 容器/指令编码)、3434 行 clang 兼容驱动、printer/C 生成方向(Vitro 是 C 的消费者非生产者)、msl/Metal/NEON experiments(LLM 基建)。kimicc 的 C 覆盖面是能力上界参照不是追赶目标——Vitro 的覆盖判据 = 语料三源 + 教学场景。
红线:禁止引入 bobzhang/cfront 作 mooncakes 依赖进核心链——引擎全域核心区裁定(三语义面注入:对照/标注/断言需要深度控制)+ 供应商单点,与 C 重写提议第三轮驳回的逻辑同构。
3. Vitro 自查三产出(本次调查最高价值)
对照 kimicc 时的三个现行问题,不依赖 kimicc 即成立,kimicc 只提供「这是真债」的第三方证据:
- codegen 成员偏移两份 inline 拷贝:
moonbit/codegen/index_member.mbt:11-45(get_member_offset)与moonbit/codegen/addr.mbt:78-84各自累加。这正是 kimicc 文档明文检讨的「duplicating aggregate layout」教训的现行缩影——kimicc 的解法是单一FieldLayout/AggregateLayout + offset_of_path(mir/pkg.generated.mbti:149-152)。去向:总计划 §10.5 G-5。 - printf/libc 事实三处分居:typeck 查格式(
typeck/check_printf_format)、codegen 决定 libc 路由(bytecode/route.mbt)、vm 分发宿主调用(vm/host_dispatch.mbt)。kimicc 用 ctype 的_info(name,arg_count)->(symbol,forwarded)?+_arg_type(symbol,index)->Type成对纯函数把同类事实压成三端共享(cfront/ctype/builtin.mbt:796-1576)。去向:总计划 §10.5 G-6。 - BytecodeGen 不依赖 typeck(类型经 annotated AST 传递,
codegen/moon.pkg只 import ast+bytecode+opcode+source)——与 kimicc 双后端在 AST 层共享度 <5%(ARM64CodegenvsX64Codegen245 方法独立、type_size双份实现)、被迫引入 MIR 补救的弯路同构。Vitro 已裁定两出口(bytecode 解释器 / bytecode→wasm)共享 bytecode 层,方向正确,保持警惕勿分叉。
4. 五维度详细情报
4.1 预处理器(cfront/preprocessor,约 5000 行)
架构事实:
- 定位是 source→source 文本前端,非 token 级组件;输出纯文本给
@parser.parse(cfront/preprocessor/README.mbt.md:3-6)。与 parser 的 lexer 完全两套实现(自有PpToken7 种 kind、无位置、无 hide set,types.mbt:97-117)——接受重复实现换两阶段解耦。 - 预处理前先一次性文本规范化:反斜杠续行拼接、注释替换为单空格(块注释保留内部换行以维持行号)、跳过字符串字面量内部(
tokenizer.mbt:2-48)。 - 宏展开引擎(
macro_expand.mbt983 行):递归控制 = 禁用集disabled : Map[String, Bool](非蓝漆)。关键细节:substitute_macro用旧 disabled 展开参数(939-941),替换结果再以 disabled+{self} 递归展开(942-949)——外层禁用名在内层参数替换时仍可见,#define vfsList GLOBAL(sqlite3_vfs*, vfsList)正确停在 vfsList。深度上限 200。##右操作数取原始未展开参数(673-681,符合标准);GNU 逗号粘贴(683-688);__VA_OPT__内递归 substitute(622-652)。无 placemarker token;粘贴出非法 token 塞PpOther整体进流(已知妥协)。 - 跨行宏调用:pending/hold 双保险——非指令行追加进
pending_tokens,只有 pending 尾部是「函数式宏名」或展开报 unterminated(清错回退)才留到下一行(preprocessor.mbt:1439-1488)。 - 「伪定义 seed」分层:不需要特判行为的 builtin 用普通
#define __has_builtin(x) 0兜底 seed 进宏表,特判命中才拦截(preprocessor.mbt:730-737)——降低特判表维护成本。 - 条件编译:四元组帧栈
ConditionalFrame{parent_active, branch_active, taken, else_seen};#elifdef/#elifndef(C23)支持。eval_if三步:defined(X)/__has_include(...)替换为 1/0 → 全展开 → 剩余PpIdent全部归一为 0 → 递归下降求值(Int64)。宽容策略:除零返回 0、未知 token 当 0 跳过、无短路(&&/||两边都求值)、无 unsigned/char/浮点语义(expr.mbt)。 - 错误传播 = 首错制:
mut error : PreprocessError?(set_error仅 None 时写入),顶层包Result——对「吞系统头继续跑」的场景比逐处 raise 省事。 - include 解析:quote/angle 搜索序可配、
#pragma once、include_next线性越过当前目录、防环双保险(include_stack + once_files);依赖追踪三件套(去重 map + system 分类 +allow_missing_includes宽容模式)供 depfile 生成。
对 Vitro 的参考点:
| 项 | 判定 |
|---|---|
disabled 集合新旧分离传递(macro_expand.mbt:938-949) |
值得对照——核对 Vitro lexer/internal/pp/expander.mbt 的 expanding_contains 是否区分「参数替换用旧集合 / 结果展开用新集合」两步,未区分则有递归误判风险 |
跨行宏调用 pending/hold 双保险(preprocessor.mbt:1439-1488) |
对照 Vitro splice.mbt 是否覆盖此场景 |
「伪定义 seed」分层(preprocessor.mbt:730-737) |
Vitro builtins.mbt 可用同样分层降低特判表成本 |
| 依赖追踪三件套 | Vitro 将来若做增量编译/depfile 直接借鉴 |
| 宽容求值器(除零=0/未知 token=0/无短路) | 不学——kimicc 宽容为吞真实系统头;Vitro cond.mbt 的严格 raise CondError + 短路跳过(skip_logical_operand)是为教学诊断,定位不同 |
| token 坐标维护 | Vitro 已覆盖(offset/line/column + pin_to_call_site;kimicc 完全没有,输出文本不可回溯位置) |
| 展开预算与诊断 | Vitro 已覆盖(budget_hit/record_trace/warn_side_effects;kimicc 只有 depth 200 一刀切) |
精读:macro_expand.mbt 全文(替换算法单函数 606-719)→ 与 Vitro expander.mbt 逐行对照的第一材料;preprocessor_test.mbt(1382 行,40+ 边界语义测试可直接移植为 Vitro 对照测试集);expr.mbt(252 行最小求值器,与 Vitro 785 行 cond.mbt 对照「宽容 vs 严格」取舍面)。
2026-09-27 三对照销项(上表「值得对照」三项,亲读 Vitro 源逐点核验):
- disabled 集合两步语义——逐点一致:Vitro
expander.mbt实参展开(:198,expand_inner(ctx, a, depth+1, expanding))发生在宏名入禁用集(:201expanding.push)之前(= 旧集合,外层禁用名仍可见);替换结果再以expanding+{self}展开(:233)——与 kimiccmacro_expand.mbt:939-949两步语义同构,连顺序都相同。无递归误判风险。 - 跨行宏调用——架构性豁免:Vitro 是「逐逻辑行词法化 → 全量 token 池 → 收尾全量展开」(
pp.mbt:236),capture_args按括号深度在 token 池扫参(expander.mbt:283),换行不产 token ⇒ 实参跨物理行天然成立;kimicc 的 pending/hold 双保险是 source→source 文本行架构特有的凑行问题。行为锚macro_call_args_across_lines(lexer_anchors_test)留痕——若未来改逐行展开架构,该锚红即架构回退警报。 - 伪定义 seed 分层——已成立(形态更彻底):Vitro
builtins.mbt自勘察 ⑤-8 起即纯数据表驱动((名, ty, text)三元组 seed 进宏表 + 少量显式特殊登记),表即 seed 层等价物;kimicc 的「伪 #define + 特判拦截」混态反而更复杂。无需动作。
4.2 解析器与 AST(cfront/parser)
架构事实:
- parser 是「读 + 折叠 + 轻语义」的聚合层:常量折叠(
const_fold.mbt)、完整 struct/union 布局引擎(位域 run 合并、匿名成员扁平化、pragma pack,const_fold.mbt:554-772)、tag 作用域、_Static_assert、轻量语义检查全在 parser 包内——理由:数组长度/位域宽度/case 值/enum 值/_Alignas都是「类型的一部分」,解析时就必须知道(parse_stmt.mbt:725-731)。数值语义全部下沉到 ctype 纯函数(@ctype.IntegerConstant{bits,ty}),parser 只做「识别 AST 形状 → 匹配算子 → 调 ctype」。 - AST 形状:
Expr28 变体的pub(all) enum,二元/一元/复合赋值算子存源码字符串(ast.mbt:24-28);节点零位置零类型(唯一例外字面量自带类型;位置只在报错瞬间从字节偏移换算,error.mbt:34-47)——文档明确记录的决策,节点位置是 Step D(语义诊断/格式保留)且未做。 - 「AST 忠实性」主张逐条核实全部兑现(#322-#326 提交系列):case/default 是语句不是合成标签(
ast.mbt:91-92,之前是Label("__kimicc_case_3_7",...)计数器烘焙进名字);局部聚合初始化器按书写记录(brace list 保持Array(Expr),之前是=0标记+逐元素赋值);每个声明名一条VarDecl(StmtList已删);匿名联合is_anonymous一等公民且进 Eq(ast.mbt:131-145);字面量保留书写文本(Number(值, 文本, 类型)三元组,文本是权威、值是折叠缓存,ast.mbt:11-17)。 - 但「不降级」有一份 12 条的已记录降级清单(忠实性的真实边界,全部有注释/提交信息/计划文档三重记录):①
Case(Int64,...)存折叠值,case RED:打成case 0:;②->脱糖为MemberAccess(Deref(expr), name)丢拼写;③_Generic解析期解析成选中分支、关联列表丢弃;④__builtin_choose_expr解析期折叠;⑤__func__直接变 String 字面量;⑥ enum 常量直接替换为int_literal;⑦ 全局char s[]="abc"展开成字符码数组而局部版本保留 String(同一构造两种表示的不对称);⑧int x={19}折叠为 19;⑨ static 局部提升为全局+名字粉碎;⑩ 块作用域 tag 冲突改名name__2而非作用域化;⑪ 一元+丢弃、空 for 条件合成1;⑫const/volatile/restrict完全消失(parse_type.mbt:136-141直接 advance 丢弃)。已知解析 bug 另有:void (*f(int))(void)重解析错、int *(*p)[2]误析构、brace elision 拒绝、1e400词法为 0.0(c-syntax-suite-plan.md:235-243)。 - 错误处理:
pub(all) suberror ParseError { Unexpected | Unsupported | Malformed }(error.mbt:59-67)——Unsupported专门给「合法 C 但 parser 子集没实现」,注释明言对库用户它和Unexpected一样常见。泛型 helper 三件套unexpected/unsupported/malformed : fn[T] ... -> T raise ParseError(parser.mbt:173-190)让 raise 点出现在任何期望类型位置。传染面实测 120 个签名(当初改造从Parser::expectraise 开始、110 编译错误、靠编译器报错迭代收敛)。abort 清点:parser 包只剩 2 个真 abort(construct API 的程序员错误),另有error_test.mbt:86-100的负向测试「no input reaches abort instead of a diagnostic」。同包两种风格并存的分界:解析错误有位置是诊断→raise;事后语义检查是对冻结 AST 的查询→Result[Unit,String](semantic_check.mbt:639)。位置精度讲究:诊断指向冒犯 token 的起点(Parser::advance记token_start),octal 坏数字报在数字本身,与 clang 对齐(int a=09;→ 1:10)。 - pragma pack 事件表方案(
pragma_pack.mbt):#pragma pack是位置的而 parser 会回溯(Lexer::posrewind 重放)——解法:预扫描成按字节偏移排序的事件表(scan37-129,正确跳过字符串/字符/注释),pack_at(pos)变成对位置的纯函数(二分查找,350-391,带proof_invariant/proof_reasoning的证明注释)。clang 兼容细节:空栈 pop 保持现值、pack(pop,N)空栈时 pop 跳过但 N 仍生效。 - Program 双视图:按种类分组
structs/globals/decls+ 按书写顺序的items : Array[TopLevel]索引带(ast.mbt:248-274)——只按组打印会破坏引用了后面函数的初始化器。
对 Vitro 的参考点:
| 项 | 判定 |
|---|---|
raise+suberror 模型 + Unsupported 独立变体 |
教学引擎同构需求——Vitro 对「合法 C 但没教到」的診断需求一样;Vitro lexer/internal/pp/cond.mbt 已用 raise CondError,不谋而合;若 Vitro parser/typeck 扩 raise 化,error.mbt 全文(<90 行)+ 泛型 helper 是 1:1 模板;代价认清:120 签名传染(Vitro 分包更多传染面只会更大);与总计划 §10.5 G-3(host raise 桥)方向一致 |
| 12 条降级清单当审计表 | Vitro 做忠实性审计直接用:-> 脱糖、enum 常量替换、__func__ 字面量化、全局/局部字符串初始化不对称……逐条问 Vitro 怎么处理的、有没有记录 |
Case/Default 作为语句 |
Vitro 若把 switch 子句建模成 label 字符串或 case 表必重蹈 __kimicc_case_3_7 覆辙——生成器无法构造、名字嗅探污染下游 |
| 初始化器按书写记录 + 付出的代价 | 展开移除暴露 7 个下游缺口(MIR 3 + codegen 4)——「parser 静默供应的行为」是隐性耦合,移除前要用真实大型 fixture 编译验证 |
| 常量折叠的分层 | AST 形状识别在 parser 方法里、数值规则在独立纯函数层、IntegerConstant{bits,ty} 类型感知——Vitro 若做常量折叠照此分层(哪些常量必须在解析期已知由语法位置决定,不是架构偏好) |
| 算子存字符串 | 反面教材勿学——使 fold_const_int 变 230 行字符串 match;Vitro 枚举算子(BinaryOp 19 个)类型安全更强,勿因「官方项目这样做」回退 |
| 节点零位置 | 勿学——Vitro 每节点带 @source.SourceLoc 是更忠实路线;kimicc 自己承认节点位置是语义诊断的前提且未做 |
Type 直接别名外部包(ast.mbt:3) |
勿学——AST 与类型模型耦合死;Vitro ast/types.mbt 独立定义是对的 |
| Program 双视图 | Vitro 有 JSON dump/打印需求时适用 |
| pragma pack 事件表 | 凡「位置敏感源码属性 + 解析器回溯」组合处都适用 |
精读:docs/c-syntax-suite-plan.md(639 行,读前 290 行——AST 忠实性工程总纲 + #325 七缺口复盘);parser/error.mbt 全文 + parser.mbt:92-190;parser/ast.mbt(313 行,每个字段 doc comment 是一条建模论证);parse_stmt.mbt:135-357(#324+#326 落地现场);const_fold.mbt:244-482。
4.3 MIR 与 ctype 语义层(cfront/ctype + mir)
架构事实:
- ctype = 纯函数形态的语义事实中心(7 文件约 3500 行,零依赖):28 变体
Type枚举(Struct(name)按名引用聚合、Aligned/Atomic包装、Named拼写占位)、整数提升/秩/公共类型/二元三元结果类型/指针算术纯函数(type.mbt:412-614)、IntegerConstant{bits,ty}折叠族(除零返回 None 不 trap,int_const.mbt:169-171)、浮点折叠/NaN-inf 位型、字符串字面量 object-size 事实、builtin 返回类型三层规则(BuiltinReturnRule枚举 →builtin_return_rule(name)→builtin_return_type(name, arg_types, choose_cond))、builtin/libcall call-shape 元数据全族(builtin.mbt:476-1576,全部是name -> (op, arg_index…)?形状的 match 纯函数 + 成对_info/_arg_type)。 - 选「纯函数」而非「数据表」的原因:查表键是二维
(name, arg_count)、结果三态(匹配/参数数不符/未知)、还必须配_arg_type给每个转发参数定型——塞不进静态表。 - 「两后端开始重复语义事实」教训的落地(
docs/mir.md:672-678明文):依赖图证据——codegen 生产 import = parser+target+ctype 不 import mir;mir_codegen 生产 import = mir+target+ctype,parser 仅for "test";@ctype.引用 262+265 处;docs 记录每张私有表被删后改为调 ctype 同名纯函数。 - MirFuncBody = typed 树形 structured IR(非 CFG/非 SSA/非线性):
MirStmt自述 "Structured MIR statement subset used before CFG/block lowering"(body.mbt:75);MirValue是 typed DAG(每节点带@ctype.Type,MirValue::ty纯结构化派生不查环境);MirLocal{id:Int,name,ty}线性 ID(参数先分配,临时$mir_tmp前缀);覆盖不足 = Option 式静默剔除(任一语句降级失败整个函数不进Program.bodies,body.mbt:4970-4996),strict codegen 同样拒绝、解释器对缺失 body 返回 Err。 - 双解释器分工:
interpreter.mbt(3295 行)= parser-AST 整数 oracle(无内存模型,字符串字面量合成指针基址,step 上限 10 万);body_interpreter.mbt(7255 行)= MirFuncBody 执行器 + 字节内存模型(多张以 Int64 地址为键的平面字节 Map + 区间注册表 + 地址规范化枢纽canonical_c_memory_addr(4289-4314)——聚合字段原始地址/alloca+50000/heap+50000 归一到同一字节地址,使 typed store/聚合赋值/memcpy/realloc 共享同一对象视图)。地址空间用魔数分区:label 4e9 起、va_list 句柄 5e9 起、va_arg 聚合 6e9 起、alloca/heap 偏移 +50000——增量承诺的债。 - 「解释器先行」纪律的证据:
docs/mir.md:683-687明文 "KeepProgram::interpret_body_i64as the first consumer for each new body feature so unsupported behavior fails explicitly before codegen depends on it";量化——mir_test.mbt中expect_body_interp(body 解释器)233 次 vsexpect_interp(旧 oracle)69 次。 - 差分测试:
test/e2e/strict_mir_codegen_test.mbt19982 行 851 个测试(640 同步 strict-MIR 单边 + 211 异步双二进制差分);harness(harness_test.mbt:150-199):同一 C 源分别经 parser-AST codegen 与 strict MIR codegen 产两个汇编、clang 分别链接两个二进制、比 exit code + stdout。 - MirModule 投影:
Program{decls, global_decls, aggregate_decls, ..., layouts, bodies}与MirModule同构但去掉priv source;layouts(目标相关计算产物)与aggregate_decls(源声明事实,target 无关)分离——同一 decl 在 Darwin ARM64 与 linux/amd64 下 layout 不同。
对 Vitro 的判断:
- Vitro 不需要 MirModule 投影——kimicc MIR 的存在理由是三个消费者(direct codegen / strict codegen / 解释器 oracle)共享语义事实;Vitro 单后端单执行器,投影层纯开销。给 Vitro 加 MIR 的唯一正当理由是出现第二个消费者(第二后端、或独立 lowering 期 oracle/诊断视图)。
- 可搬的是「语义事实是包不是层」:Vitro 对应物是把 UAC/尺寸/成员偏移/libc 调用形状抽成 ctype 式词汇包(零依赖纯函数),
ast只留语法——见 §3 的 G-5/G-6。 - 勿退化内存模型:kimicc 的 Map+区间注册换取「只模拟触碰过的字节」;Vitro
vm/memory的 1MB 线性内存+受检单入口更接近真实机器且快照便宜(时间旅行依赖),不换。若 Vitro 将来做等价解释器,地址空间分区应显式枚举而非魔数。 - Vitro 现有
@ast.compute_type_size已被 typeck/codegen 双端委托(单源),但无对齐概念(Pointer=4、无 padding)——这是教学模型(ILP32)的自觉裁剪不是缺陷;借鉴方向是「单源」不是「补全 ABI」。
精读:cfront/ctype/type.mbt 全文(765 行,C 子集语义词汇包最小完整形态);cfront/ctype/builtin.mbt:1-800(_info/_arg_type 成对纯函数模式——G-6 的重组范本);mir/body.mbt 骨架(1-160 + 157-430 + 4970-4996 三段);body_interpreter.mbt 的 1-120 + 828-940 + 4289-4345(内存模型最小样本);docs/mir.md:672-695 + test/e2e/harness_test.mbt:150-199。
4.4 codegen 与 JIT(codegen / mir_codegen / jit / cmd)
架构事实:
- 双管线并存:
parse → codegen(直接,AST 一步到汇编)与parse → mir → mir_codegen(strict)。codegen 包同一 AST 三个出口:汇编文本 / Mach-O .o 字节 / JIT image(公开 API 仅 3 函数 + 2 struct)。 - 「直出 Mach-O 字节」的实际实现是文本汇编→重新解析→指令编码:
generate_macho_object先产汇编文本,再由约 2200 行手写 ARM64 mini 汇编器(约 150 个encode_*函数)+ 约 1500 行 Mach-O 容器(header magic 0xfeedfacf、LC_SEGMENT_64/LC_SYMTAB/LC_DYSYMTAB 手写、nlist 16 字节符号表、ARM64_RELOC_PAGE21/PAGEOFF12/BRANCH26 重定位)拼出字节(macho_object.mbt:2204起)。价值=e2e 验证「direct macho object links and runs」、单二进制完整-c路径。 - 固定 scratch 寄存器纪律(无寄存器分配器):
x9=唯一结果、x10=右操作数、x11-x13=临时、x15=间接 callee(docs/codegen-registers-and-abi.md:18-39官方表格);二元求值 = 用物理寄存器模拟深度 1 的栈机:gen_expr(left)→x9→str x9,[sp,#-16]!→gen_expr(right)→x9→mov x10,x9→ldr x9,[sp],#16→op x9,x10(expr.mbt:4472-4490)——与栈机 push/pop 一一同构。C 变量全部常驻栈槽;局部寻址 offset 从 0 向上增长记录local_offsets,访问off = offset - frame_size相对x29(func.mbt:277-340);块作用域 map copy/restore(stmt.mbt:40-48)。 - 两阶段调用序列(
expr.mbt:4860-4945):先对每个实参 ABI 分类累计临时区大小 → 全部实参先求值进临时栈槽 → 再统一重载进 x0-x7/v0-v7——避免嵌套调用 clobber 半就绪的参数寄存器。 - switch 无跳转表:线性
cmp+je链;switch_clauses.mbt的「位置配对协议」——预分发 label、按 body 遍历顺序发牌、末尾check_switch_clauses_emitted校验全部消费。 - JIT image 五字段:
code(TEXT 节在前)/executable_size(16KB 页对齐)/base_relocations(u32 偏移数组)/external_relocations(offset+长度+符号名私有编码)/symbols;外部 branch stub 每个 16 字节(ldr x16,[pc+8]; br x16);加载器协议(W^X 正确顺序,jit/stub.c:139-206):mmap RW 匿名页 → memcpy code → 应用 base relocations(每槽 += 镜像基址)→ external relocations(dlsym(RTLD_DEFAULT),下划线前缀自动重试)→__builtin___clear_cache→ 最后 mprotect 可执行前缀 R|X——全程无 RWX 页;MoonBit 侧 247 行 + C stub 254 行。 - x64 后端与 ARM64 后端共享度 <5%(245 方法独立、
type_size双份)——kimicc 自己为此引入 MIR 补救(mir_codegen 的Codegen带可选mir_module字段,type_size 优先用 MIR 事实)。 - strict 契约 = 双预检谓词(
mir_codegen/codegen.mbt:1231-1242):mir_body_codegen_func_supported(与发射器共用同一套遍历逻辑)+mir_global_init_supported,任一不覆盖返回 Err 才进 unchecked 生成;fallback 组合子在独立 80 行包(mir_codegen_compat/compat.mbt:50-58)。 - cmd/main = 3434 行 clang 兼容 drop-in 驱动(
-c/-S/-E/-M/-MM/-MD/.../@response-file,链接全委托 clang,.d依赖文件自产)。
对 Vitro 的判断:
| 项 | 判定 |
|---|---|
| JIT stub.c + image 格式 | 模板超级指令 JIT 路线复活时的完整 W^X 先例(约 500 行总代价:image 生成 + 加载器)——基础设施部分直接对标;kimicc 本身不是模板 JIT(它是把编译好的机器码搬进内存),但「image 格式 + 重定位编码 + W^X 顺序 + dlsym 外部解析」这四件是任何 JIT 落地都要重做的部分。去向:总计划 S9「JIT 复核」注记 |
| strict+fallback 组合子包形态 | wasm 生成器 rollout 可照抄——strict 谓词预检返回 Result(不覆盖走 Err)+ 独立 compat 包组合旧路径 fallback;「预检谓词与发射器共用实现」证明可行 |
| switch 位置配对协议 + PendingBranch 回填 | 对 Vitro 任何需要回填偏移的字节出口(含 wasm 模块 branch immediates)是同构问题,成本极低可搬 |
| switch 线性比较链 | 无跳转表在 sqlite 级正确性验证下足够——Vitro 的 bytecode switch 若无跳转表 opcode 可引为先例 |
| 固定寄存器纪律 | 反向确认栈式路线——kimicc 本质是用寄存器模拟栈机;Vitro 直接用真栈机更简单,存在性证明反过来支持已裁定 |
| 寄存器经验→wasm local 槽分配 | 借不到(查证结论)——kimicc 的 spill 是深度 1 显式单值临时栈,不存在「求值栈临时提升为 local 槽」的分配器;C 变量全常驻栈槽,比 wasm locals 更保守 |
| 双后端 AST 层零共享 | 反面教材——Vitro 两出口共享 bytecode 层(已裁定)恰是前车之鉴 |
| 文本汇编两段式 | 对 Vitro wasm 字节出口是多余一跳,直接 emit 字节即可;文本快照测试的便利不值得 |
| 3434 行驱动 | 教学引擎无独立 CLI 工具链需求,无参考 |
精读:jit/stub.c + jit/jit.mbt(条件性——模板 JIT 复活时);mir_codegen_compat/compat.mbt(80 行);codegen/switch_clauses.mbt(94 行);codegen/func.mbt(474 行,一次遍历+栈槽+显式 spill 的最小切片);docs/codegen-registers-and-abi.md(「无分配器的求值契约」文档化到可测试粒度的样本——Vitro 给自研 opcode 写语义文档时可对标章节结构)。
4.5 测试防线与工程纪律(test/ 36k 行 + CI + docs)
七层测试金字塔(全仓约 2560 测试块 / 66k 行测试代码):
| 层 | 形态 | 判定面 | 规模 |
|---|---|---|---|
| L1 单测 | _test 黑盒 / _wbtest 白盒;汇编文本快照 inspect() |
文本快照 / assert_eq | codegen_test 9820 行 373 测试 1658 处 inspect;mir_test 6927 行 324 |
| L1.5 属性测试 | quickcheck + shrink,独立包 qc_test(781 行 8 测试) |
print→parse 回到同一 AST + 打印文本 fixed point | check(count=300, max_size=12, seed=20260812) + 双种子(seed=427 再跑 300——注释明说「防止回归躲在幸运语料后面」);生成器按「parser 的像」构造并把每条 AST 规范化规则写成注释清单;反例经 @debug.Debug 打成 C 文本;手写 one-step shrink |
| L2 差分 | 双二进制:parser-AST codegen vs strict MIR codegen | exit code + stdout(不比二进制字节) | 851 测试(640 同步单边 + 211 异步双二进制);测试结构元闸 check-strict-mir-interop.sh(awk 检查每个生成器组必有 clang 互操作测试——「测试的测试」) |
| L3 MIR oracle | 编译产物 exit code vs 进程内解释器 interpret_i64 |
数值 | 73 async 测试;无需外部工具可移植 |
| L4 e2e | 编译→链接→运行;MoonBit async 驱动(明文弃 shell 脚本,sqlite-conformance-plan.md:25) |
exit code / stdout | e2e_test 213;macho_object_test 86(直出 .o 链接运行);c4.c(C in four functions 自举解释器)编译链接冒烟 |
| L5 conformance | SQLite 3.49.1(9.7MB vendored amalgamation)四编译模式矩阵(Kimicc/Clang/DirectObject/StrictMir);TinyCC/QuickJS/zlib/Lua 等 9 个外部 testbed env 门控 | 上游公开测试套件 + 双构建 residual 对拍 | veryquick 全量 330,516 测试仅余 24 失败且 clang-built 同样失败;分级 opt-in(KIMICC_SQLITE_CONFORMANCE=release/.../full);公共测试树不 vendored——Fossil tarball URL + SHA-256 锚定,缺失则 skip marker rc=0 |
| L6 printer 防线 | 自我回环 + 外部接受性双层 | roundtrip(两次打印相等 + AST 相等)+ clang -fsyntax-only |
printed_output_compiles_test.mbt(98 行):每个 error: 行必须匹配 allowed 白名单(已知缺陷诊断串,修一个删一个)、clang 非零退出但无识别 error 也 fail(防 clang 没跑溜过闸);注释直说要害——「回环用自家 parser,自洽 fixed point 可能是无效 C;唯一办法是交给另一个编译器」(QuickJS 夹具曾打出 1174 clang 错误而 roundtrip 全绿) |
夹具管理策略:小夹具 vendored + 大源码树 fetch-to-/tmp 不进库;git commit SHA pin 且三处一致性机器检查(fetch 脚本 ↔ ci.yml env ↔ docs 逐字比对);SQLite tarball SHA-256 在档;fetch 脚本工程化(3 次退避重试、--depth=1 直取 pin、失败分级加深);预检与获取分离(check 脚本只验路径存在不下载)。
async e2e 最小形态:test/e2e/stub.c 仅 25 行(moonbit_write_file + moonbit_system 两个 extern "c")+ @fs.read_file 读回输出 + async test 块被 moon test 原生调度 + 确定性 /tmp 路径(31 进制字符串哈希);子进程不靠 async 库靠 C stub;超时靠 C driver 内 alarm(10)。
CI(ci.yml 85 行 2 jobs):macos-latest 装 MoonBit(工具链未 pin 浮动)→ bash -n 全部脚本语法检查 → fixture pins 检查 → moon fmt 后 git diff --exit-code → moon check → moon info 后 git diff --exit-code(.mbti 接口漂移即红)→ e2e 逐文件单独 moon test(隔离+失败定位);job 2 = linux-amd64-smoke(Docker 重建镜像跑交叉后端冒烟,含 expect_compile_failure 负向探针);copilot-setup-steps.yml = GitHub Copilot coding agent 环境预置。
conformance 作战文档(docs/sqlite-conformance-plan.md 465 行):目标→约束→当前基线清单→上游来源+SHA→6 级 opt-in gate 命令→发布检查单(含「先发 cfront 后发 kimicc,否则 pin 指向无人能解析的版本」)→ Failure Ledger(351-447,每 bug 一行 = 复现 moon test --filter 命令 + 根因 + 修复 commit;45+ 个 compiler bug 无一丢失回归);执行原则 "Fix one compiler failure class at a time with focused e2e regressions"。文档-代码一致性机器闸:check-sqlite-conformance-plan.sh 把测试里的脚本路径数组逐条 grep 回 plan 文档。
对 Vitro 的增量(十四闸/shadow/vm_diff 之外五件,按性价比):
- quickcheck 属性测试三件套——Vitro 的 Clang golden baseline 是固定用例集,属性测试扫的是用例集外空间;三件套 = 按「AST 规范像」写生成器(规范化规则注释化)+ 固定种子双跑 + 反例打印成可读文本。
- 「测试的测试」元闸——awk 检查测试文件结构(每个生成器组必有互操作测试、每个支持的 builtin 必被测试引用);Vitro 十四闸没有这一层。
- allowed-residue 白名单闸——层 2 Clang 直拍(§7.1)落地时直接可用:golden 比输出之外再比「Clang 接受性 + 诊断面」,已知缺陷白名单修一个删一个。
- Failure Ledger——每 bug 一行「复现命令+根因+修复」;Vitro 的缺陷修复批可低成本入册。
- pin/doc/脚本三处一致性机器闸 + 分级 opt-in conformance + skip marker——防文档与脚本脱节;重闸 env-gated 手动跑并记录 residual 是否 clang-matching。
CI 对照:kimicc 的 fmt/check/info diff-gate 式 hygiene(跑完看 git diff)比 Vitro 现行 hygiene job(只跑 check)更严,值得吸收;工具链浮动未 pin 与 Vitro「工具链追新+probe 红才升级」政策相反——Vitro 政策保持。async e2e 最小形态对 Vitro 的 cmd/ 工具可参考(25 行 stub + 判定收进 MoonBit)。
精读:test/e2e/harness_test.mbt(1245 行,全部判定原语最小实现——层 2 直拍 harness 直接模板);cfront/qc_test/gen_test.mbt + roundtrip_qc_test.mbt(489 行);docs/sqlite-conformance-plan.md 全文(conformance 推进方法论);test/e2e/printed_output_compiles_test.mbt(98 行);.github/workflows/ci.yml + scripts/native-ci-test.sh(124 行)。
5. 精读清单汇总(按 Vitro 待办对应)
| Vitro 待办 | 精读文件 |
|---|---|
| 层 2 Clang 直拍(总计划 §7.1) | test/e2e/harness_test.mbt、test/e2e/printed_output_compiles_test.mbt |
| 防线增量五件(总计划 §7.1 注记) | cfront/qc_test/gen_test.mbt + roundtrip_qc_test.mbt、docs/sqlite-conformance-plan.md |
| wasm 生成器 rollout | mir_codegen_compat/compat.mbt、codegen/switch_clauses.mbt |
| 错误处理 / AST 忠实性审计 | parser/error.mbt、parser/ast.mbt、docs/c-syntax-suite-plan.md |
| 模板 JIT(条件性,复活时才读) | jit/stub.c、jit/jit.mbt |
| 语义事实重组(G-5/G-6 参照) | cfront/ctype/type.mbt 全文、cfront/ctype/builtin.mbt:1-800 |
| S7/S8 MoonBit 编码风格 | 全仓即范本;展品两件:parser/pragma_pack.mbt:350-391(带 proof 的二分)、parser/construct.mbt(trait + #alias DSL) |
6. 后续工作项(已排版进总计划)
| 项 | 落点 | 内容 |
|---|---|---|
| G-5 成员偏移单点化 | 总计划 §10.5(新增行) | 消 codegen 两份 inline 拷贝(§3.1),参照 kimicc FieldLayout/offset_of_path 单点 |
| G-6 printf/libc 事实重组 | 总计划 §10.5(新增行) | 消 typeck/codegen/vm 三处分居(§3.2),参照 ctype _info/_arg_type 成对纯函数模式 |
| 层 2 直拍参照物注记 | 总计划 §7.1 层 2 行(注记) | harness 模板 = test/e2e/harness_test.mbt;白名单闸 = printed_output_compiles_test.mbt |
| 防线增量五件 | 总计划 §7.1 末注记 | quickcheck 属性测试 / 元闸 / allowed-residue(已随 clang_direct 的 known_direct.json 部分落地)/ Failure Ledger / 三处一致性闸——2026-09-27 更新:层 2 已落地,触发线已到;立独立批(quickcheck 属性测试三件套为首,扫固定语料集外空间) |
| kimicc 12 条降级清单当审计表 | S8 差异台账批(2026-09-27 登记) | §4.2 的 12 条(-> 脱糖/enum 常量替换/__func__ 字面量化/全局局部字符串初始化不对称等)逐条对照 Vitro 与 Rust oracle 的处理方式——Vitro 忠实性审计的现成清单 |
| S9「JIT 复核」参照注记 | 总计划 §10 S9 行(注记) | W^X 加载器先例 = jit/stub.c:139-206 + image 格式四件套 |
7. 调查方法与诚实边界
- 主会话亲读:README.mbt.md(449 行)/ AGENTS.md / docs 六份(2845 行)/ git log 1467 提交 / moon.mod×3 / LICENSE / mooncakes 发布状态(bobzhang/cfront 0.3.1 在架,页面提示该版本构建失败文档未生成但可安装,21 下载)。
- 五个并行探索代理(Explore,very thorough):预处理器 / 解析器与 AST / MIR 与 ctype / codegen 与 JIT / 测试防线与工程纪律——全部结论带
文件:行号,关键主张有交叉验证(如 851 测试的 640+211 分解、disabled 集合语义、首错制传播)。 - 未实测运行 kimicc(本机 ARM64 Windows 与其主支持目标 ARM64 macOS 不匹配;未尝试 wasm target 构建——对参考价值评估无增量)。
- Vitro 侧对照全部亲证:包结构、
codegen/moon.pkg依赖、index_member.mbt/addr.mbt偏移拷贝、typeck/check_printf_format/bytecode/route.mbt/vm/host_dispatch.mbt三分居、pp/cond.mbtraise 风格。 - 本报告不含任何待验证的「据称」;数字对代理实测值,无法复核处已标注。