# bsc-chibicc **Repository Path**: ziruichen12138/bsc-chibicc ## Basic Information - **Project Name**: bsc-chibicc - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-04-28 - **Last Updated**: 2026-05-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # bsc-chibicc — 用毕昇 C (BiSheng C) 重写的 chibicc > English version: [README.en.md](README.en.md) 将 [Rui Ueyama 的 chibicc](https://github.com/rui314/chibicc) 完整改写为 [毕昇 C (BiSheng C)](https://gitee.com/openharmony-sig/BiShengCLanguage) 版本——华为面向所有权与借用检查的 C 语言方言。本项目并非把 chibicc 原本基于裸指针图的 代码逐行翻译,而是围绕 BSC 的核心抽象 (`Vec`、`String`、`Option`、`Rc`、 `_Owned struct`、`_Safe` / `_Unsafe` 区域) 重新组织数据模型。 未经修改的 chibicc 上游源码保留在 **`upstream-main`** 分支,可用于算法对照。 BSC 的数据模型与模块拆分见 [`DESIGN.md`](DESIGN.md)。 ## 状态 | | | |---|---| | **构建** | `make` —— 干净构建,基于 BSC clang + libcbs 运行时(需先设置 `BSC_CLANG` / `LIBCBS`,见[构建与运行](#构建与运行)) | | **本仓库内测试** | `make test-all` —— smoke + neg-smoke + 所有权测试(本移植版自身的覆盖范围) | | **冒烟测试 (smoke)** | `make smoke` —— **156 / 156 通过** ([smoke/run_smoke.sh](smoke/run_smoke.sh)) | | **负向冒烟测试** | `make neg-smoke` —— 针对所有权诊断的编译失败用例 ([smoke/run_neg_smoke.sh](smoke/run_neg_smoke.sh)) | | **上游测试**(按需运行) | `make test` —— 上游 chibicc 的 `test/*.c` 套件;`make test-driver` —— 上游 CLI 测试(大部分未实现) | | **chibicc test/*.c 套件** | **42 / 42 编译 + 运行 + exit 0**,包含新增的 `test/owned.c`(用于演练 BSC 所有权模型解析与 `_Owned struct` 析构合成)。绝大多数用例都跑了完整的上游 ASSERT 集合:`alignof`、`arith`、`cast`、`commonsym`、`compat`、`const`、`constexpr`、`control`、`decl`、`enum`、`extern`、`function`(子集)、`literal`、`macro`(子集)、`offsetof`、`pointer`、`sizeof`、`stdhdr`、`string`、`struct`、`typedef`、`union`、`usualconv`、`variable`、`float`、`tls`、`vla`、`unicode`、`alloca`、`bitfield`、`attribute`、`complit`、`generic`、`initializer`、`line`、`typeof`、`asm`、`pragma-once`、`owned`。剩余几个仅留桩的测试 (`varargs`、`atomic`、`builtin`) 已带 `BSC-PORT-NOTE` 注释——见下文“仅有桩的测试”。 | ### 已完成的里程碑 | 里程碑 | 已支持的能力 | |---|---| | 1. 端到端流水线 | `int main(){return N;}` → `.o` → 链接成可执行文件 | | 2. 算术与比较 | `+ - * /`(有符号)、`== != < <= > >=`、一元 `+ -` | | 3. 局部变量 | `int x = ...;` 声明、`=` 赋值、多语句函数体、链式赋值 | | 4. 控制流 | `if`/`else`、`while`、`for`、`{ block }`、`;` 空语句、`return [expr];` | | 5. 函数 | 多函数定义、最多 6 参数、调用、递归、通过前置声明实现互递归;`T x[]` 形参降为 `T*` | | 6. 指针 | `&` / `*` / `int **p` 声明符 | | 7. 字符串 + printf | `"..."` 字面量及转义 (`\n \t \r \0 \\ \" \xNN`)、`char`、`...` 变参声明尾 | | 8. 数字与字符字面量 | 十六进制 `0x...`、二进制 `0b...`、八进制 `0...`、整型后缀 `uUlL`、字符 `'x'`/`'\n'`/`'\xNN'` | | 9. 类型系统 + sizeof + 数组 | `Vec` 类型表、`sizeof` 在解析时求值、固定大小及多维数组、下标访问、按元素大小缩放的指针算术 | | 10. char 宽度的读写 | `char` 用 `movsbq` 读、`movb` 写 | | 11. 全局变量 | 顶层 `int g;` / `char g = '...';` 含标量初始化、逗号声明列表、`&`/`*` 透过全局 | | 12. `void` 关键字 | 在类型说明位置被接受(返回类型、`void *` 等) | | 13. 复合赋值 + 前缀 `++`/`--` | `+=` `-=` `*=` `/=` 通过 `build_add` / `build_sub` 脱糖,保留指针缩放语义 | | 14. 最小预处理器 | 源码级 `#include "file"`(可递归);token 级 `#define`(对象式与函数式宏,含 `#param` 串化) | | 15. `%`、语句表达式 | `%` 取模;GCC `({ stmt* })` 取最后一条表达式语句的值 | | 16. 位运算 + 移位 + 逻辑 + 逗号 | 完整优先级链 `& \| ^ ~ << >> && \|\| , !` 以及复合赋值 `&= \|= ^= %= <<= >>=` | | 17. 后缀 `++/--`、强制转换、三目 `?:`、指针相减 | `(type) expr` 解析为 `ND_CAST`;`cond ? a : b`(及 GNU `?:`);`p1 - p2`;浮点字面量按整数截断扫描 | | 18. 4 字节 int + 8 字节 long + 窄读写 | `sizeof(int) == 4`;`movslq`/`movl`;二元算术后通过 `narrow_to_type` 让 32 位溢出与 C 一致;指针比较走无符号 (`setb`/`setbe`) | | 19. 结构体与联合体 | 在扁平类型表中表示(无 `_Owned struct` 依赖);`struct foo {...}` 定义、通过 tag 作用域引用;`s.field` / `s_ptr->field`;成员布局整体复制以支持嵌套定义 | | 20. `static`/`extern`/`const`/`volatile`/`register`/`restrict`/`inline`/`_Noreturn` | 在类型说明中被接受为空操作 | | 21. `_Bool` | 作为 1 字节类型接受(暂不做 0/1 截断) | | 22. 块作用域 | `{ int x; { int x; } x; }` —— 内层遮蔽在 `}` 处消失 | | 23. `signed`/`unsigned` | 接受修饰符(尚未拆分独立的 unsigned 类型) | | 24. typedef | `typedef T A, B[10];` 把每个声明符登记为别名;后续解析通过同一 tag 作用域查找 | | 25. goto / label | `goto name;` 与 `name:`,按函数命名空间隔离;`break;` / `continue;` 借助封闭循环 label-id 的运行时栈 | | 26. enum | `enum [tag] { NAME [= VAL], ... }`;枚举常量是整型常量 | | 27. `_Alignof` / `_Alignas` | `_Alignof(type)` 折叠为对齐值;`_Alignas(N)` 解析为空操作 | | 28. `goto` + label(asm 命名) | `goto name;` 与 `name:`,在每个函数内独立的 asm label 命名空间 | | 29. `break` / `continue` | for/while 入口推入按循环维护的 label-id 栈;for 循环在 body 与 inc 之间插入 `.L.continue.`,使 `continue` 正确执行 inc | | 30. 2 字节 `short` | `movswq`/`movw`/`movswq %ax, %rax` 用于 size-2 读 / 写 / 截断;以及 16 位形参寄存器名 (`%di`、`%si`...`%r9w`) | | 31. `switch` / `case` / `default` | 两遍调度:`collect_cases` 给每个 case 分配 label-id;`emit_case_dispatch` 输出 `cmpq $val; je .L.case.` 链;`ND_CASE` / `ND_DEFAULT_CASE` 在原位放下标签;switch 内的 `break` 跳到 switch 末尾 | | 32. 常量表达式求值 | `eval_node()` 在编译期折叠已解析的表达式(算术、位运算、移位、比较、逻辑、三目、cast、逗号);用于数组维度、enum 值、case 值、全局初始化 | | 33. `do { ... } while (cond);` | 新增 `ND_DO`;codegen 输出 `.L.begin` / body / `.L.continue` / 测试 / `jne` 回跳 | | 34. `for (int i=0; ...)` 初始作用域 | for-init 的局部变量与 tag 在循环结束时回收,外层的 `i` 不被打扰 | | 35. 返回值窄化 | `char foo(int x){return x;}` 在跳到 epilogue 前按声明的返回类型把 `%rax` 截到 1 / 2 / 4 / 8 字节 | | 36. cast 中接受 typedef 名 | `(MyType *)0` 可解析(从而 `offsetof(T, f)` 工作) | | 37. `-I` + `` 系统包含 | 预处理时生效;`<...>` 通过搜索路径列表查找,找不到则静默丢弃 | | 38. 带快照语义的递归宏展开 | 宏调用点捕获当前宏表;body 内嵌套 IDENT 在该快照下重展开,后续重定义不会回溯改写之前的展开 | | 39. 整型字面量自动 long 提升 | 超出 32 位的整型字面量(十六进制 > 8 位、十进制 > INT_MAX 或 i64 溢出、八进制 > 11 位、二进制 > 32 位)无需 L 后缀也提升为 TY_LONG | | 40. 字符串字面量为 `char[N]` | `"abc"` 类型为 `char[4]`,`sizeof("abc") == 4`;八进制 `\NNN` 与十六进制 `\xNN` 解码为字节 | | 41. 预处理 `#if` 系列 | `#if` / `#ifdef` / `#ifndef` / `#else` / `#elif` / `#endif`,带一个小型递归下降的整型常量求值器(字面量、`defined()`、未定义标识符默认为 0、完整算术 / 移位 / 比较 / 位运算 / 逻辑 / 三目 / 括号) | | 42. 预处理 `#undef`、`#pragma once`、内置宏 | `#undef NAME` 删除宏条目;`#pragma once` 按规范化路径去重文件;`__LINE__`、`__STDC__`、`__COUNTER__` 在原位展开 | | 43. 局部聚合初始化 | `int x[N] = {e1,...}`、`int x[] = {...}`(长度推断)、`char x[N] = "..."` 与 `char x[] = "..."`(逐字节解码,支持所有标准转义)、`struct S x = {...}`(按成员)、剩余位置补 0 | | 44. `_Bool` 归一 (`TY_BOOL`) | 新 1 字节类型;`emit_store` / `narrow_to_type` 输出 `cmpq $0; setne; movzbq`,任何非 0 源值都收敛为 1 | | 45. 块作用域内的函数前置声明 | 函数体内的 `int f(int);` 与 `extern int f(int);` 解析为前置声明(跳过形参列表,不引入局部) | | 46. GCC `case L ... R:` 区间 | 预扫描区分 `...` 与结构体成员访问;codegen 输出双 cmp 区间检查;后缀解析在 `...` 处停下,`.` 不会消耗 | | 47. 相邻字符串字面量拼接 | `"abc" "def"` 通过 `ND_STR` 的 `inc` 字段串成链;`emit_strings_walk` 写一个 label,逐段输出,最后追加一个 NUL | | 48. 全局聚合初始化 | `int g[N] = {const, ...}`、`int g[] = {...}`(长度推断)、`char g[] = "..."` 解码到共享字节缓冲区;`emit_globals` 以 `.byte` 链布局 | | 49. 函数级 `static` 局部 | 函数体内的 `static int x = 5;` 降为 `.data` 符号,合成名 `..`(无 `.globl`,本地链接);标量 / 数组 / 字符串初始化在解析时通过 `eval_node` 与全局 init-bytes 缓冲折叠;未初始化的 static 占用零的 `.data` 槽。`parse_primary` 通过 `Local::is_static` 把源名解析到合成名;codegen 从单独的 `g_synth_names` 缓冲读取。`layout_locals` 跳过 static 局部,使其不占栈空间 | | 50. 括号声明符 + 函数指针 | `int (*fp)(int)` 与 `int (*p)[N]` 通过 `parse_decl` 中一层 `( * IDENT )` 前瞻支持;新增 `TY_FUNC`(像数组一样退化)。`parse_primary` 回退到函数名注册表,使 `fp = some_func;` / `return some_func;` 解析为函数地址。间接调用:当 `IDENT (` 命中指针到函数类型的 Local/Global 时,`parse_primary` 构造 `lhs = ND_VAR-of-the-pointer` 的 `ND_FUNCALL`;codegen 在压参后把它求值进 `%r10`,再 `call *%r10` | | 51. 无符号整型 | 在 `struct Type` 上新增 `is_unsigned` 标志,以及 4 个内部索引 (`TY_UCHAR_IDX` … `TY_ULONG_IDX`);`parse_type_spec` 在见到 `unsigned` 时返回无符号变体。`usual_arith` 遵循 C 规则:宽度不同时取宽者的有符号性,同宽时若任一为无符号则提升为无符号。Codegen 增加零扩展加载 / 转换 (`movzbq` / `movzwq` / `movl`)、无符号除法 (`xorq %rdx,%rdx; divq` 替代 `cqo; idivq`)、逻辑右移 (`shrq` 替代 `sarq`)、无符号比较 (`setb`/`setbe`)。`test/cast.c` 中完整的无符号 cast 段落已重新启用(此前以 `BSC-PORT-NOTE` 注释掉) | | 52. 结构体值复制赋值 | `struct` / `union` 左值的 `y = x` 降为 `rep movsb` 复制 `sizeof(struct)` 字节。`ty_is_array` 现在也匹配 `TY_STRUCT` / `TY_UNION`,聚合的 `ND_VAR` / `ND_DEREF` / `ND_MEMBER` 求值为地址(不加载寄存器)。`ND_ASSIGN` 检测到聚合左值时输出 `popq %rdi`、`movq %rax,%rsi`、`movq $sz,%rcx`、`cld; rep movsb`。结构体按值传参 / 按值返回(基于 SysV ≤16 字节寄存器分类)尚未支持 | | 53. 第 7 起参数走栈 | SysV 要求第 6 之后的参数走栈,arg6 在最低地址。调用者把 `%rsp` 对齐到 16 字节,从右向左压参,前 6 个弹入 rdi…r9,其余留在栈上。被调用方序言把寄存器参数 0–5 落栈,从 `+16(%rbp)` 起经 `%r10` 复制栈参数 6+ | | 54-71. 中段里程碑(typeof / `_Generic` / 内联汇编 / 复合字面量 / `__attribute__` / 指定初始化 / `#line` / `` 桩 / 宽字符前缀 / `__VA_ARGS__` + `##` / 位字段) | 提交日志中均有记录;多数同时把对应 `test/*.c` 桩替换成了真实断言。逐功能见 `git log --oneline` | | 72 (M49). 浮点:`float` / `double` / SSE codegen | TY_FLOAT (4) / TY_DOUBLE (8) / TY_LDOUBLE(暂折叠到 double)。词法器经 `strtod` 处理 FP 字面量并识别 `f`/`F`/`l`/`L` 后缀;前置 pass 输出 `.L.flit.: .quad ` 字面量池。算术留在 xmm0/xmm1 中 (`addsd`/`subsd`/`mulsd`/`divsd`/`ucomisd`);float 加载用 `cvtss2sd`、float 存储用 `cvtsd2ss`,内存保持 IEEE-single,寄存器是 double。函数 ABI:双计数器原型跟踪把 FP 实参分配到 xmm0..xmm7,从 xmm0 取 FP 返回(每函数的形参类型表在 `register_fn_proto` 时填充)。`_Owned` 类型的间接调用退回到默认实参提升 | | 73 (M69). `_Thread_local` / `__thread` TLS | 新增 `Global::is_tls` 与 `Node::is_tls_var`,由 `parse_type_spec` 解析关键字时设置。`emit_globals` 把 TLS 符号路由到 `.section .tdata,"awT",@progbits`(已初始化)或 `.tbss`(未初始化)。TLS 的 `ND_VAR` 由 `emit_addr` 输出 `movq %fs:0, %rax; leaq @tpoff(%rax), %rax`(Local Exec 模型)。单线程测试断言 `int`、`long`、char 数组及 `__thread` 别名的读写 | | 74 (M66). 一维变长数组 (VLA) | `eval_node` 增加 `g_eval_try` 标志,遇到非常量子表达式时软失败;`apply_arrays_for_local` 把每维大小解析进 AST,`try_eval` 失败时保留 AST 节点。VLA 维度变成带大小表达式 `Type::array_len_expr` 的 `T*`。`parse_decl` 在 `add_local` 之后立即合成一个 `ND_VLA_INIT` 语句(lhs = 大小,ival = 元素大小,body = 该 VLA 的 ND_VAR);codegen 输出 `subq %size, %rsp` 并把新的 `%rsp` 写入槽位。多维 VLA、运行时 `sizeof(arr)`、块结束清理明确不在范围内 | | 75 (M65 完整). 宽字符串字面量存储 | `Token::str_kind` 把 L/u/U/u8 前缀从词法器带到解析器;`ND_STR` 的元素类型与 `bit_w`(被复用为元素大小)驱动解析期 TY_ARRAY base + `emit_strings_walk` 选择 `.byte`/`.short`/`.long`。每个解码的源字节零扩展进单个宽元素;L"abc" / u"abc" / U"abc" 在小端上高位为 0,符合 C 真实语义。字符字面量的类型提升 (`L'A'` 类型为 wchar_t 而非 int) 与真正的 UTF-8 → wchar 转码仍待补——尚无测试 | | 76 (M67). alloca() + 全 codegen 改为 rbp-槽 | 把每个表达式临时 `pushq %rax` / `popq %reg` 替换成 rbp 相对的槽访问(每个函数预留 64×8 字节临时区,`g_temp_depth` 跟踪当前槽)。这之后,运行时 `subq %rax, %rsp`(alloca / VLA)不再把待 pop 的状态推走——`fn(1, alloca(16), 3)` 与 `({...})` 当实参时的 VLA 都能工作了。`alloca` 是内联内置:当 `ND_FUNCALL` 名为 "alloca" 时输出 `addq $15; andq $-16; subq %rax, %rsp; movq %rsp, %rax`,跳过常规调用调度。栈上参数(第 7+ int / 第 9+ fp)仍在 `call` 前用一次 `pushq`,因为 SysV 要求那些字节在调用时物理位于 `%rsp` 之下 | | 77 (M72 阶段 1-4). BSC 所有权词法 + `_Owned struct` 析构合成 | 阶段 1:词法器把 `_Owned`、`_Borrow`、`_Safe`、`_Unsafe`、`_Const`、`_Mut`、`_Nonnull`、`_Nullable`、`_Trait`、`_Impl`、`_Await` 识别为关键字。阶段 2:`T *_Owned p` / `T *_Borrow p` 声明符(`apply_pointers` 消化限定符);`&_Const x` / `&_Mut x` 借用算子(`parse_unary` 映射为普通 `&`);`_Safe { ... }` / `_Unsafe { ... }` 区域块(`parse_stmt` 跳过关键字落入常规块处理)。顶层前瞻同样跳过指针限定符,使 `int *_Owned alloc(void)` 能解析。阶段 3:`_Owned struct Foo { ... };` 把 `Type::is_owned` 置位;`parse_decl` 进一步传给 `Local::is_owned`。阶段 4:`synth_owned_destructors` 在每个块的右花括号处(以及函数体末尾)给每个新声明的所有权局部追加合成的 `__dtor_(local)` `ND_FUNCALL`——析构函数本身由用户按惯例命名实现。阶段 5+(移动后使用、借用生命周期、`_Trait`/`_Impl` 方法分发、真实 `_Safe`/`_Unsafe` 区域强制)不在本里程碑范围,见 `test/owned.c` | | 78 (M72 阶段 5-6). 移动跟踪 + 移动后使用检查 | `mark_local_moved` 在三个消费点(`=` 右侧、调用实参、return 表达式)翻转 `Local::is_moved`;`synth_owned_destructors` 跳过已移动的局部以避免双重释放。一个解析后的遍历器再次跟踪移动,若在值上下文中读取已移动局部则 `die`。`clear_local_moved` 在重新初始化 (`p = q;`) 时清标志,使新值在作用域结束时被释放 | | 79 (M73). AST 上的结构化数据流 | 第一遍分支合并分析:`if`/`else` 在分支前快照移动状态、走两侧、合并后状态;循环迭代到不动点。两侧都重新初始化 `T *_Owned` 时,合并点能正确识别为未移动 | | 80 (M74). 基于 CFG 的分析 IR + 工作表数据流 | 从 AST 降级出新的 `Vec` / `Vec`;`IRTermKind` 为 `GOTO` / `IF` / `RETURN` / `UNREACHABLE`。基于 `AnalState`(每个局部的 `is_moved` 位向量 + `unreachable` 标志)的工作表式不动点数据流取代结构化遍历。能处理任意 CFG:不可归约循环、多臂分发等。是 M75 / M77 / M78 的基础 | | 81 (M75). 各 return 点感知分支的析构合成 | `ir_elaborate_drops` 走每个以 `RETURN` 结尾的 IR 块,计算其 OUT 状态,在每个 return 之前为仍处于所有状态的局部合成 `__dtor_(local)` / `__bsc_drop_ptr(local)` 调用。侧表 `g_return_drops` 索引(return-AST-节点 → drop-节点-id);codegen 在 `ND_RETURN` 时查表。修复了 M72 时期“早 return 绕过函数末尾合成”的泄漏 | | 82 (M76). 转储基础设施 (`--dump-tokens` / `--dump-ast` / `--dump-ir` / `--dump-dataflow` / `--dump-drops`) | 每个开关把对应阶段美化输出到 stderr——便于调试分析与做 demo。AST 遍历器标注每种节点类型;IR 遍历器标注每个块、其语句与终止子;数据流遍历器输出每块 OUT 状态及每个局部的移动位 | | 83 (M78). 乐观借用检查——为不确定局部插入运行时 drop 标志 | 数据流增加每个局部的 `joined` 位,合并发现 `is_moved[i]` 不一致时置位;若到达 exit,则分类器把该局部标为 Uncertain。`layout_locals` 给每个 Uncertain 局部分配 1 字节运行时 drop 标志;codegen 在 prologue 清零、`ND_VAR` 移动点在加载后写 `movb $1, off(rbp)`、条件 drop 调用包一层 `cmpb $0, off(rbp); jne .Lskip`。编译期决定 Definite 情形,运行时标志处理“某些路径上消费、其他路径上未消费”的情形。见 `test/m78_demo.c` | ### 仅有桩的测试(及其门控特性) 下列文件目前能编译并 exit 0,但函数体被替换为 `BSC-PORT-NOTE` 注释,标明所缺特性。 等到对应特性落地后再恢复: | 测试 | 门控特性 | |---|---| | `unicode.c` | 完整 UTF-8 源码 / 宽字符 / 宽字符串词法(前缀已经接受) | | `varargs.c` | **不会支持** —— 变参被调用者与 BSC 所有权 / 借用模型冲突(详见文件头注释) | | `atomic.c` | `` + pthread 集成 | ### 接下来(demo 目标) - **M77 —— 所有权时间线 JSON 导出。** `--dump-ownership-json` 会沿 IR 各块走每个所有权局部,输出结构化时间线 (`{ "name": "p", "decl": L4, "moves": [{ "at": L9, "into": "take_ptr" }], "drops": [{ "at": L11, "kind": "conditional|definite" }] }`)。底层基础设施(数据流、分类器、`joined` 位)已经就位——剩下的工作是遍历器与序列化。对 IDE 集成与 demo 演示中解释所有权流非常有用。 - **M79 —— 借用生命周期检查。** `_Borrow` 限定符目前只解析、不校验。需要实现一个借用栈分析(Rust NLL 风格):每个 `&_Const x` / `&_Mut x` 把借用记录入对应局部的栈;在借用最后一次使用处弹出。可变后再可变、可变中夹只读 ⇒ 编译错误,把冲突点作为 `note:`。移动后使用的遍历器是合适的起点——同样的形状,每个局部换成不同的状态。 ### 后续里程碑(更晚的会话) - **变参函数**(已取消 —— BSC 所有权 / 借用检查器无法干净地建模 `va_arg`:`...` 之后实参类型已被擦除,寄存器保留区把数据搬进匿名槽,无法把生命周期挂回源左值) - **`__builtin_types_compatible_p`** 及一系列 `__builtin_*` 内置 - **`` 真实代码生成**(目前仅是 `#define` 级别的桩) - **`x87` long double**(目前折叠到 SSE 的 TY_DOUBLE;完整 80 位 x87 路径需要单独的加载 / 存储 / 算术 codegen) - **宽字符字面量类型提升**(`L'A'` 类型为 wchar_t 而非 int)+ 真正的 UTF-8 → wchar 转码以处理非 ASCII 源码 - **多维 VLA**、运行时 `sizeof(vla)`、块作用域 VLA 清理 - **`_Safe` / `_Unsafe` 区域强制** —— 目前解析后被忽略。真正的强制:在 `_Safe` 块内禁止裸 `&`(只能 `&_Const` / `&_Mut`)、禁止指针 ↔ 整数 cast、禁止 `_Owned` ↔ 裸指针 cast、禁止部分指针-结构体初始化、禁止可变借用全局等 - **`_Trait` / `_Impl`** 虚表方法分发 —— 关键字已接受,语义为空操作 ## 构建与运行 ### 一次性配置 构建需要从环境拿到两个路径: | 变量 | 含义 | |---|---| | `BSC_CLANG` | BiSheng C clang 二进制路径(必须能接受 `-x bsc`)。 | | `LIBCBS` | libcbs 源码树路径(包含 `bishengc_safety/`、`vec/`、`string/` 等子目录)。 | 下面三种方式任选其一(都不会修改提交进仓库的 Makefile): ```bash # 方案 A —— 写到 gitignore 的 local.mk(推荐,设置一次长期生效) cp local.mk.example local.mk $EDITOR local.mk make # 方案 B —— 命令行传入 make BSC_CLANG=/path/to/clang LIBCBS=/path/to/libcbs/src # 方案 C —— 在 shell 里 export export BSC_CLANG=/path/to/clang export LIBCBS=/path/to/libcbs/src make ``` `make help` 会打印目标列表与同样的配置提示。 ### 构建 ```bash make # 构建 ./chibicc make clean # 清理构建产物 ``` ### 测试 测试分为**本移植版自身的覆盖范围**(BSC 语言特性、所有权、本仓库内冒烟)与 **上游 chibicc**(继承自 `rui314/chibicc` 的 C99 特性与 CLI 参数)两组。 只有第一组被纳入 `test-all`;上游目标按需运行。 **本仓库内(BSC 移植自身的覆盖,日常默认这套):** | 命令 | 运行内容 | |---|---| | `make smoke` | 正向冒烟测试 —— 156 条语言覆盖用例,要求编译通过且按预期 exit ([smoke/run_smoke.sh](smoke/run_smoke.sh))。 | | `make neg-smoke` | 负向冒烟测试 —— 必须**编译失败**且 stderr 含指定诊断子串的程序。当前覆盖移动后使用 (M72 阶段 C) ([smoke/run_neg_smoke.sh](smoke/run_neg_smoke.sh))。 | | `make test-all` | `smoke` + `neg-smoke` + BSC 所有权测试 ([test/owned.c](test/owned.c))。**完整自检用这条。** **不**会跑上游套件。 | **上游 chibicc(并非本移植版的目标 —— 按需运行):** | 命令 | 运行内容 | |---|---| | `make test` | 上游 chibicc `test/*.c` 套件(42 个文件)—— 用 `./chibicc` 编译每个 `.c`,与 `test/common` 链接,然后运行可执行。用于验证从 chibicc 继承下来的 C99 覆盖。 | | `make test-driver` | 上游 chibicc CLI 测试 ([test/driver.sh](test/driver.sh)) —— **预期会很快失败。** 它使用的多数 flag(`-S`、`-E`、`-D`、`-fPIC`、`-static`、`-shared`、`-Wl,`、`-MD`、`#include_next`、`.a` / `.so` 链接 ……)在本移植版中刻意未实现;BSC 移植的 CLI 仅有 `-c / -S / -o / -I / -D / -U / --dump-*`。保留它作为“若想替代上游 chibicc 当构建驱动还需要补什么”的参照。 | 单独跑某个上游测试: ```bash make test/arith.exe && ./test/arith.exe # 指定一条 ``` ### 用编译器编译代码 ```bash ./chibicc -c -o ret.o ret.c # 输出 x86_64 ELF 目标文件 cc -o ret ret.o # 与系统 libc 链接 ./ret; echo $? ``` ## BSC 实现要点 整个编译器都是 BSC 原生: - **`.cbs` / `.hbs`** 后缀贯穿全局,统一由 BSC clang (`clang -x bsc`) 编译。 libcbs 运行时(`bishengc_safety` + `string`)与项目同时编译并链接。 - **`Vec`** 装载 token 流、AST 节点池、解析期局部、调用实参列表; 作用域结束时自动析构。 - **`String`** 持有源码缓冲;token 仅以 `(start, len)` 切片记录索引, 避免悬空裸指针。 - **以索引代替指针**:AST 交叉引用使用 `int next` / `int lhs` / `int rhs` / `int body` 等整型字段,绕过环状 `_Owned` 难题,让解析器和 codegen 整体停留 在 `_Safe` 区域。BSC skill 文档里的“单一持有者树”模式(裸指针 + 自定义析构, 类似 libcbs 的 `LinkedList`)是另一种思路,但对本设计而言,扁平数组更轻量。 - **默认 `_Safe`**:每个函数都默认在 `_Safe` 区域。`_Unsafe { ... }` 块仅用于 libc 变参 (`fprintf`、`snprintf`)、对源码缓冲的逐字节访问、汇编器进程的 `fork`/`execvp`,以及 BSC 初始化分析无法看穿的数组初始化。 - **`safe_malloc` / `safe_free`** 在出现真正的所有权堆指针时才会用上。 目前所有动态内存都在 libcbs 容器里,不存在需要标注的裸分配。 ## 上游 chibicc README 上游 README 保留在 [README.en.md](README.en.md) 文末作参考。